﻿namespace TomatoBreak.Infrastructure
{
    using System;
    using System.Dynamic;
    using System.Linq.Expressions;

    public class DynamicPropertiesMetaObject : DynamicMetaObject
    {
        public DynamicPropertiesMetaObject(
            Expression parameter, IHasDynamicProperties value) :
            base(parameter, BindingRestrictions.Empty, value)
        {
        }

        public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }

            var method = LimitType.GetMethod("get_" + binder.Name);
            Expression getterExpression;

            if (method == null)
            {
                method = LimitType.GetMethod("GetProperty");

                getterExpression = Expression.Call(
                    Expression.Convert(Expression, LimitType),
                    method,
                    new Expression[] { Expression.Constant(binder.Name) });
            }
            else
            {
                getterExpression = Expression.Call(
                    Expression.Convert(Expression, LimitType), method);
            }

            var getProperty = new DynamicMetaObject(
                getterExpression,
                BindingRestrictions.GetTypeRestriction(Expression, LimitType));

            return getProperty;
        }

        public override DynamicMetaObject BindSetMember(
            SetMemberBinder binder, DynamicMetaObject value)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }

            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            var method = LimitType.GetMethod("set_" + binder.Name);
            Expression setterExpression;

            if (method == null)
            {
                method = LimitType.GetMethod("SetProperty");

                var arguments = new Expression[]
                                    {
                                        Expression.Constant(binder.Name),
                                        Expression.Convert(
                                        value.Expression, typeof(object))
                                    };

                setterExpression = Expression.Call(
                    Expression.Convert(Expression, LimitType),
                    method,
                    arguments);
            }
            else
            {
                setterExpression = Expression.Call(
                    Expression.Convert(Expression, LimitType),
                    method,
                    new[]
                        {
                            Expression.Convert(value.Expression,
                            value.LimitType)
                        });
            }

            var wrapped = Expression.Block(
                setterExpression, Expression.New(typeof(object)));

            var setProperty = new DynamicMetaObject(
                wrapped,
                BindingRestrictions.GetTypeRestriction(Expression, LimitType));

            return setProperty;
        }
    }
}